#include "drv_iic.h"

#define SCL_H GPIO_SetBits(GPIOB, GPIO_Pin_6)
#define SCL_L GPIO_ResetBits(GPIOB, GPIO_Pin_6)
#define SDA_H GPIO_SetBits(GPIOB, GPIO_Pin_7)
#define SDA_L GPIO_ResetBits(GPIOB, GPIO_Pin_7)
#define READ_SDA GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7)
#define ACK 0  //低电平
#define NoACK 1	//高电平

//发送数据：drv_iic_start() → drv_iic_senddata(设备地址) → drv_iic_waitACK() → 发送数据 → drv_iic_stop()
//接收数据：drv_iic_start() → drv_iic_senddata(设备地址) → drv_iic_waitACK() → 接收数据 → drv_iic_stop()
//SCL  PB6
//SDA  PB7

// 注：I2C通信中，数据线SDA是双向的,数据线只有一根：
// - 主机发送数据给从机时，SDA作为输出
// - 主机读取从机数据时，SDA作为输入
/*

数据传输：
主机发送数据(ACK/NoACK)到从机
主机从从机接收数据
应答机制：
主机发送应答 / 非应答信号
主机等待从机的应答信号
*/

/**
 * @brief  初始化I2C引脚
 * @note   配置PB6(SCL)和PB7(SDA)为开漏输出模式
 */
void drv_iic_init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;
	//开漏
	GPIO_InitStruct.GPIO_OType=GPIO_OType_OD;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;//设置为浮空
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOB, &GPIO_InitStruct);
	
}

/**
 * @brief  配置SDA为输入模式
 * @note   用于主机读取从机数据时
 */
void drv_iic_in(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;

	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;//设置为浮空
	
	GPIO_Init(GPIOB, &GPIO_InitStruct);
}

/**
 * @brief  配置SDA为输出模式
 * @note   用于主机发送数据给从机时
 */
void drv_iic_out(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;

	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_OUT;
	//开漏
	GPIO_InitStruct.GPIO_OType=GPIO_OType_OD;
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;
	GPIO_InitStruct.GPIO_PuPd=GPIO_PuPd_NOPULL;//设置为浮空
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	
	GPIO_Init(GPIOB, &GPIO_InitStruct);
}

/**
 * @brief  发送I2C起始信号
 * @note   起始信号：SCL高电平时，SDA从高到低的跳变
 */
void drv_iic_start(void)
{
	SCL_L;//将时钟线拉低，使数据无效
	drv_iic_out();//将数据线设置为输出模式
	
	SDA_H;//将数据线拉高
	SCL_H;//拉高时钟线时，数据线有效
	delay_us(5);//延时5us,满足IIC时序要求
	SDA_L;//拉低数据线，产生下降沿
	delay_us(5);//延时5us,满足IIC时序要求
	SCL_L;//拉低时钟线，占用总线,用于继续执行
	
}

/**
 * @brief  发送I2C停止信号
 * @note   停止信号：SCL高电平时，SDA从低到高的跳变
 */
void drv_iic_stop(void)
{
	SCL_L;//将时钟线拉低，使数据无效
	drv_iic_out();//将数据线设置为输出模式
	
	SDA_L;//将数据线拉低
	SCL_H;//拉高时钟线时，数据线有效
	delay_us(5);//延时5us,满足IIC时序要求
	SDA_H;//拉低数据线，产生上升沿
	delay_us(5);//延时5us,满足IIC时序要求
}

/**
 * @brief  主机发送应答信号(ACK)
 * @note   应答信号：SCL高电平时，SDA保持低电平
 */
void drv_iic_sendACK(void)
{
	SCL_L;//将时钟线拉低，使数据无效
	drv_iic_out();//将数据线设置为输出模式

	SDA_L;//将数据线拉低
	SCL_H;//拉高时钟线时，数据线有效
	delay_us(5);//延时5us,满足IIC时序要求
	SCL_L;//拉低时钟线，占用总线
	SDA_H;
}

/**
 * @brief  主机发送非应答信号(NoACK)
 * @note   非应答信号：SCL高电平时，SDA保持高电平
 */
void drv_iic_sendNoACK(void)
{
	SCL_L;//将时钟线拉低，使数据无效
	drv_iic_out();//将数据线设置为输出模式

	SDA_H;//将数据线拉高
	SCL_H;//拉高时钟线时，数据线有效
	delay_us(5);//延时5us,满足IIC时序要求
	SCL_L;//拉低时钟线，占用总线
	SDA_L;
}

//主机等待从机的应答，是一个输入的过程
/**
 * @brief  主机等待从机的应答信号
 * @return 0-收到应答(ACK)，1-未收到应答(NoACK)
 * @note   从机应答时会拉低SDA线
 */
uint8_t drv_iic_waitACK(void)
{
	uint16_t tim_out=0;
	SCL_L;//将时钟线拉低，使数据无效
	drv_iic_in();//将数据线设置为输入模式

	SCL_H;//拉高时钟线时，数据线有效
	while(READ_SDA)
	{
		tim_out++;
		if(tim_out>500)//500次为应答则stop
		{
			drv_iic_stop();//主机停
			return NoACK;
		}
	}
	SCL_L;//拉低时钟线，占用总线
	return ACK;
}


/**
 * @brief  主机向从机发送一个字节数据
 * @param  data: 要发送的字节数据
 * @note   I2C通信时数据高位先行
 */
void drv_iic_senddata(uint8_t data)
{
	uint8_t i=0;
	SCL_L;//将时钟线拉低，使数据无效
	drv_iic_out();//将数据线设置为输出模式

	//串行通信 8位数据位 发8次
	for(i=0;i<8;i++)
	{
		//高位先行 判断第8位是否为1
		if(data&0x80)
		{
			SDA_H;  // 发送高电平（1）
		}
		else
		{
			SDA_L;// 发送低电平（0）
		}
		SCL_H;//拉高时钟线时，数据线有效
		delay_us(1);
		SCL_L;//拉低时钟线，占用总线
		delay_us(1);
		data=data<<1;// 数据左移一位，准备发送下一位,将次高位变为新的最高位
	}
}

/**
 * @brief  主机从从机读取一个字节数据
 * @param  flag_ack: 读取后是否发送应答(ACK-0, NoACK-1)
 * @return 读取到的字节数据
 * @note   I2C通信时数据高位先行
 */
uint8_t drv_iic_receivedata(uint8_t flag_ack)
{
	uint8_t receive_data=0,i=0;
	SCL_L;//将时钟线拉低，使数据无效
	drv_iic_in();//将数据线设置为输入模式，准备读取数据
	for(i=0;i<8;i++)
	{
		receive_data=receive_data<<1;// 数据左移一位，准备接收下一位
		SCL_H;//拉高时钟线时，数据线有效
		if(READ_SDA)//高电平进入if
		{
			receive_data|=0x01;// 如果是高电平，设置最低位为1
		}
		delay_us(1);
		SCL_L;//拉低时钟线，占用总线
		delay_us(1);
	}
	if(flag_ack==ACK)
	{
		drv_iic_sendACK();
	}
	else
	{
		drv_iic_sendNoACK();
	}
	SCL_L;//将时钟线拉低，使数据无效
	return receive_data;
}
